﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace LostLib.Collections.Generic
{
	/// <summary>
	/// Hashes function values for given inputs.
	/// </summary>
	/// <typeparam name="TKey">Input type</typeparam>
	/// <typeparam name="TValue">Output type</typeparam>
	[Serializable]
	[ComVisible(false)]
	public class Hasher<TKey, TValue>: IDictionary<TKey, TValue>
	{
		private Func<TKey, TValue> mGenerator;
		private Dictionary<TKey, TValue> mDictionary;
		bool mValidated = false;

		/// <summary>
		/// Controls validating for Hasher
		/// </summary>
		public bool Validating
		{
			get { return mValidating; }
			set
			{
				if (value == mValidating)
					return;

				if (value && !mValidated)
					Validate();

				mValidating = value;
			}
		} bool mValidating = false;

		/// <summary>
		/// Creates an instance of Hasher using given function
		/// </summary>
		/// <param name="generator">Hashed function</param>
		public Hasher(Func<TKey, TValue> generator)
		{
			mGenerator = generator;
			constructorCheckGenerator();

			mDictionary = new Dictionary<TKey, TValue>();
		}

		/// <summary>
		/// Creates an instance of Hasher using given function and some precomputed values
		/// </summary>
		/// <param name="generator">Hashed function</param>
		/// <param name="precomputedValues">Precomputed values</param>
		public Hasher(Func<TKey, TValue> generator, IDictionary<TKey, TValue> values)
		{
			mGenerator = generator;
			constructorCheckGenerator();

			mDictionary = new Dictionary<TKey, TValue>(values);
		}
		/// <summary>
		/// Validates all hashed values
		/// </summary>
		/// <remarks>
		/// This computes values for each stored item via generator and compares them to hashed values
		/// </remarks>
		public void Validate()
		{
			foreach (KeyValuePair<TKey, TValue> pair in this)
				if (!mGenerator(pair.Key).Equals(pair.Value))
					throw new InvalidProgramException();

			mValidated = true;
		}

		/// <summary>
		/// Creates an instance of Hasher using given function and custom key comparer
		/// </summary>
		/// <param name="generator">Hashed function</param>
		/// <param name="comparer">Custom key comparer</param>
		public Hasher(Func<TKey, TValue> generator, IEqualityComparer<TKey> comparer)
		{
			mGenerator = generator;
			constructorCheckGenerator();

			mDictionary = new Dictionary<TKey, TValue>(comparer);
		}

		/// <summary>
		/// Creates an instance of Hasher using given function and approximate capacity
		/// </summary>
		/// <param name="generator">Hashed function</param>
		/// <param name="capacity">Prospective capacity value</param>
		public Hasher(Func<TKey, TValue> generator, int capacity)
		{
			mGenerator = generator;
			constructorCheckGenerator();

			mDictionary = new Dictionary<TKey, TValue>(capacity);
		}

		private void constructorCheckGenerator()
		{
			if (mGenerator == null)
				throw new ArgumentNullException("generator");
		}

		#region IDictionary<TKey,TValue> Members

		private void ValidateAndThrowExcepion(TKey key, TValue value)
		{
			if (!mValidating) return;
			if (!mGenerator(key).Equals(value))
				throw new InvalidProgramException(); 
		}

		/// <summary>
		/// Adds precomputed value to Hasher
		/// </summary>
		/// <param name="key">Input</param>
		/// <param name="value">Precomputed output</param>
		public void Add(TKey key, TValue value)
		{
			ValidateAndThrowExcepion(key, value);

			mDictionary.Add(key, value);
		}

		/// <summary>
		/// Determines if value is hashed for given input.
		/// </summary>
		/// <param name="key">Function input</param>
		/// <returns>True if hashed. Else false.</returns>
		public bool ContainsKey(TKey key)
		{
			return mDictionary.ContainsKey(key);
		}

		/// <summary>
		/// Returns a collection of hashed inputs
		/// </summary>
		public ICollection<TKey> Keys
		{
			get
			{
				return mDictionary.Keys;
			}
		}

		/// <summary>
		/// Removes given input from Hasher
		/// </summary>
		/// <param name="key">Function input</param>
		/// <returns>If key was in Hasher this returns true else false.</returns>
		public bool Remove(TKey key)
		{
			return mDictionary.Remove(key);
		}

		/// <summary>
		/// Gets function value for the given input.
		/// </summary>
		/// <param name="key">The input for value to get.</param>
		/// <param name="value">Returns value.</param>
		/// <returns>Always returns true.</returns>
		/// <remarks>This method computes value using generator if it is not hashed.</remarks>
		public bool TryGetValue(TKey key, out TValue value)
		{
			if (!mDictionary.TryGetValue(key, out value))
			{
				value =	mGenerator(key);
				Add(key, value);
			}
			return true;
		}

		/// <summary>
		/// Gets a collection of hashed values.
		/// </summary>
		public ICollection<TValue> Values
		{
			get { return mDictionary.Values; }
		}
		/// <summary>
		/// Gets function value for the given input.
		/// </summary>
		/// <param name="key">The input for value to get.</param>
		/// <returns>Function value.</returns>
		public TValue this[TKey key]
		{
			get
			{
				TValue value;
				TryGetValue(key, out value);
				return value;
			}
			set
			{
				ValidateAndThrowExcepion(key, value);

				mDictionary[key] = value;
			}
		}

		#endregion

		#region  Members

		private ICollection<KeyValuePair<TKey, TValue>> GenericCollection
		{
			get { return mDictionary as ICollection<KeyValuePair<TKey, TValue>>; }
		}
		void System.Collections.Generic.ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
		{
			ValidateAndThrowExcepion(item.Key, item.Value);

			GenericCollection.Add(item);
		}
		
		/// <summary>
		/// Removes all hashed items from Hasher.
		/// </summary>
		public void Clear()
		{
			mDictionary.Clear();

			mValidated = true;
		}

		bool System.Collections.Generic.ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
		{
			return GenericCollection.Contains(item);
		}

		void System.Collections.Generic.ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
		{
			GenericCollection.CopyTo(array, arrayIndex);
		}

		/// <summary>
		/// Gets the number of hashed values.
		/// </summary>
		public int Count
		{
			get { return mDictionary.Count; }
		}

		bool System.Collections.Generic.ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
		{
			get
			{
				return GenericCollection.IsReadOnly;
			}
		}

		bool System.Collections.Generic.ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
		{
			return GenericCollection.Remove(item);
		}

		#endregion

		#region IEnumerable<KeyValuePair<TKey,TValue>> Members

		private IEnumerable<KeyValuePair<TKey, TValue>> GenericEnumerable
		{
			get { return mDictionary as IEnumerable<KeyValuePair<TKey, TValue>>; }
		}

		/// <summary>
		/// Returns an iterator that enumerates through this instance.
		/// </summary>
		/// <returns>An iterator.</returns>
		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
		{
			return mDictionary.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GenericEnumerable.GetEnumerator();
		}

		#endregion
	}
}
